package com.hk.core.service.jpa.impl;

import com.hk.commons.query.Order;
import com.hk.commons.query.QueryModel;
import com.hk.commons.query.SortUtils;
import com.hk.commons.query.page.QueryPage;
import com.hk.commons.query.page.SimpleQueryPage;
import com.hk.commons.util.AssertUtils;
import com.hk.commons.util.CollectionUtils;
import com.hk.commons.util.ObjectUtils;
import com.hk.core.data.jpa.repository.BaseJpaRepository;
import com.hk.core.jdbc.query.ConditionQueryModel;
import com.hk.core.service.jpa.JpaBaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.UnaryOperator;

/**
 * @author kevin
 * @date 2018-10-10 10:30
 */
public abstract class JpaServiceImpl<T extends Persistable<ID>, ID extends Serializable>
        implements JpaBaseService<T, ID> {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private Class<T> domainClass;

    protected abstract BaseJpaRepository<T, ID> getBaseRepository();

    protected ExampleMatcher ofExampleMatcher() {
        return ExampleMatcher.matchingAll().withIgnoreNullValues();
    }

    /**
     * Example 查询参数不能为null
     *
     * @param t t
     * @return t
     */
    private T checkNull(T t) {
        return ObjectUtils.defaultIfNull(t, BeanUtils.instantiateClass(getDomainClass()));
    }

    @SuppressWarnings("unchecked")
    private Class<T> getDomainClass() {
        if (Objects.isNull(domainClass)) {
            ResolvableType resolvableType = ResolvableType.forClass(getClass());
            this.domainClass = (Class<T>) resolvableType.getSuperType().getGeneric(0).resolve();
        }
        return domainClass;
    }

    @Override
    public void deleteById(ID id) {
        getBaseRepository().deleteById(id);
    }

    @Override
    public void delete(T entity) {
        getBaseRepository().delete(entity);
    }

    @Override
    public void delete(Iterable<T> entities) {
        getBaseRepository().deleteAll(entities);
    }

    @Override
    public T insert(T t, UnaryOperator<T> operator) {
        return getBaseRepository().save(operator.apply(t));
    }

    @Override
    public Collection<T> batchInsert(Iterable<T> entities) {
        return CollectionUtils.toCollection(getBaseRepository().saveAll(entities));
    }

    @Override
    public Optional<T> findById(ID id) {
        return Objects.isNull(id) ? Optional.empty() : getBaseRepository().findById(id);
    }

    @Override
    public List<T> findAll(Order... orders) {
        return CollectionUtils.toList(getBaseRepository().findAll(SortUtils.toSort(orders)));
    }

    @Override
    public List<T> findByIds(Iterable<ID> ids) {
        return CollectionUtils.toList(getBaseRepository().findAllById(ids));
    }

    @Override
    public boolean existsById(ID id) {
        return getBaseRepository().existsById(id);
    }

    @Override
    public long count() {
        return getBaseRepository().count();
    }

    @Override
    public T updateById(T t, UnaryOperator<T> function) {
        AssertUtils.isTrueWithI18n(!t.isNew(), "update.id.notEmpty");
        return getBaseRepository().save(function.apply(t));
    }

    @Override
    public Optional<T> findOne(T t) {
        return getBaseRepository().findOne(Example.of(checkNull(t), ofExampleMatcher()));
    }

    @Override
    public T getById(ID id) {
        return getBaseRepository().getReferenceById(id);
    }

    @Override
    public List<T> findAll(T t, Order... orders) {
        return getBaseRepository().findAll(Example.of(checkNull(t), ofExampleMatcher()), orders);
    }

    @Override
    public QueryPage<T> queryForPage(QueryModel<T> query) {
        return getBaseRepository().queryForPage(Example.of(checkNull(query.getParam()), ofExampleMatcher()),
                query.getOrders(), query.getStartRowIndex(), query.getPageSize());
    }

    @Override
    public QueryPage<T> queryForPage(ConditionQueryModel queryModel) {
        return getBaseRepository().queryForPage(queryModel);
    }

    @Override
    public long count(T t) {
        return getBaseRepository().count(Example.of(checkNull(t), ofExampleMatcher()));
    }

    @Override
    public boolean exists(T t) {
        return getBaseRepository().exists(Example.of(checkNull(t), ofExampleMatcher()));
    }

    @Override
    public List<T> batchUpdate(Collection<T> entities) {
        return getBaseRepository().saveAll(entities);
    }

    @Override
    public Optional<T> findOne(Specification<T> spec) {
        return getBaseRepository().findOne(spec);
    }

    @Override
    public QueryPage<T> findAll(Specification<T> spec, int pageIndex, int pageSize, Order... orders) {
        Page<T> page = getBaseRepository().findAll(spec, PageRequest.of(pageIndex, pageSize, SortUtils.toSort(orders)));
        return new SimpleQueryPage<>(page.getContent(), page.getTotalElements(), pageIndex, pageSize);
    }

    @Override
    public List<T> findAll(Specification<T> spec, Order... orders) {
        return getBaseRepository().findAll(spec, SortUtils.toSort(orders));
    }

    @Override
    public long count(Specification<T> spec) {
        return getBaseRepository().count(spec);
    }
}
